package dbi.org.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map.Entry;

import dbi.org.api.RDBMS;
import dbi.org.exception.ColumnAlreadyExistsException;
import dbi.org.exception.ColumnDoesntExistsException;
import dbi.org.exception.ColumnNameEmptyException;
import dbi.org.exception.ConstraintIsNull;
import dbi.org.exception.DbNameDoesntMatchException;
import dbi.org.exception.PrimaryKeyColumnDoesntExist;
import dbi.org.exception.PrimaryKeyDoesntExist;
import dbi.org.exception.PrimaryKeyHasNoColumns;
import dbi.org.exception.PrimaryKeyValueAlreadyExists;
import dbi.org.exception.RecordNamesAndValuesNotEqual;
import dbi.org.exception.RecordValueTypeDoesntExist;
import dbi.org.exception.ReferenceDoesntExistsException;
import dbi.org.exception.ReferenceRecordException;
import dbi.org.exception.ReferenceToGroupDoesntExist;
import dbi.org.exception.TableDoesntMatchException;
import dbi.org.exception.TableNameEmptyException;
import dbi.org.exception.ValueCannotBeEmptyException;
import dbi.org.exception.ValueNotFoundException;

public class Table {
	private String tableName;
	private String dbName;
	private LinkedHashMap<String, Column> columns;
	private Constraint constraint;
	private ArrayList<Record> records;

	// Constraint moet er nog bij Deze later weghalen
	public Table(String eenTabelnaam, String dbName) {
		tableName = eenTabelnaam;
		this.dbName = dbName;
		columns = new LinkedHashMap<String, Column>();
		setConstraint(new Constraint("null"));
		records = new ArrayList<Record>();
	}

	public Table(String eenTabelnaam, String dbName, String consName) {
		tableName = eenTabelnaam;
		this.dbName = dbName;
		columns = new LinkedHashMap<String, Column>();
		setConstraint(new Constraint(consName));
	}

	public String getDbName() {
		return dbName;
	}

	public void setDbName(String dbName) {
		this.dbName = dbName;
	}

	/**
	 * 
	 * @param eenNaam
	 * @param theColumn
	 * @throws DbNameDoesntMatchException
	 * @throws TableDoesntMatchException
	 * @throws ColumnAlreadyExistsException
	 * @throws Exception
	 */
	public void addColumn(Column theColumn) throws DbNameDoesntMatchException,
			TableDoesntMatchException, ColumnAlreadyExistsException {
		if (theColumn.getTableName().equals(tableName)) {
			if (theColumn.getDbName().equals(dbName)) {
				// checken of er al een veld bestaat met dezelfde naam
				if (columns.get(theColumn.getName()) == null) {
					columns.put(theColumn.getName(), theColumn);
				} else {
					throw new ColumnAlreadyExistsException(theColumn.getName());
				}
			} else {
				throw new DbNameDoesntMatchException();
			}
		} else {
			throw new TableDoesntMatchException("Tabelnaam komt niet overeen");
		}
	}

	public void removeColumn(String columnName)
			throws ColumnDoesntExistsException, ColumnNameEmptyException {
		if (!columnName.equals("")) {

			if (columns.get(columnName) != null) {
				columns.remove(columnName);
			} else {
				throw new ColumnDoesntExistsException("Column bestaat niet");
			}
		} else {
			throw new ColumnNameEmptyException();
		}
	}

	public Column findKolom(String kolomNaam)
			throws ColumnDoesntExistsException, ColumnNameEmptyException {
		if (!kolomNaam.equals("")) {
			Column veld = columns.get(kolomNaam);
			if (veld != null) {
				return veld;
			} else {
				throw new ColumnDoesntExistsException("Column bestaat niet");
			}
		} else {
			throw new ColumnNameEmptyException();
		}

	}

	/**
	 * 
	 * @return
	 */
	public LinkedHashMap<String, Column> getColumns() {
		return columns;
	}

	/**
	 * @return the tabelnaam
	 */
	public String getTableName() {
		return tableName;
	}

	/**
	 * @param tableName
	 *            the tabelnaam to set
	 * @throws TableNameEmptyException 
	 */
	public void setTableName(String theTableName) throws TableNameEmptyException {
		if(theTableName.equals("")) {
			throw new TableNameEmptyException();
		} else {
			tableName = theTableName;
		}
		
	}

	public Constraint getConstraint() {
		return constraint;
	}

	public void setConstraint(Constraint constraint) {
		this.constraint = constraint;
	}

	public void addReference(Reference ref) {
		constraint.addsingleReference(ref);
	}
	
	public void removeReference(String refName) throws ReferenceRecordException, ReferenceDoesntExistsException {
		if(records.size() == 0) {
			constraint.removeReference(refName);
		} else {
			throw new ReferenceRecordException("Kan referentie niet verwijderen er zijn nog records in Table: " + this.tableName);
		}
		
	}

	public void addRecord(String[] names, String[] values)
			throws ColumnDoesntExistsException, RecordNamesAndValuesNotEqual,
			ValueCannotBeEmptyException, RecordValueTypeDoesntExist,
			PrimaryKeyColumnDoesntExist, PrimaryKeyDoesntExist,
			ReferenceToGroupDoesntExist, PrimaryKeyHasNoColumns,
			ConstraintIsNull, PrimaryKeyValueAlreadyExists {

		if (names.length != values.length) {
			throw new RecordNamesAndValuesNotEqual();
		}

		for (int i = 0; i < names.length; i++) {
			if (!columns.containsKey(names[i])) {
				throw new ColumnDoesntExistsException("Column " + names[i]);
			}
		}

		for (int i = 0; i < values.length; i++) {
			// controleren van de waardes
			Type type = columns.get(names[i]).getColumntype();
			if (type == Type.VARCHAR) {

			} else if (type == Type.INTEGER) {
				try {
					Integer.parseInt(values[i]);
				} catch (NumberFormatException ex) {
					throw new RecordValueTypeDoesntExist(
							"Is geen goede waarde van het Type INT");
				}
			} else if (type == Type.BOOLEAN) {
				if (values[i].equals("true")) {

				} else if (values[i].equals("false")) {

				} else {
					throw new RecordValueTypeDoesntExist(
							"Is geen goede waarde van het Type Boolean");
				}
			}

		}
		// Controleren op Null
		for (int i = 0; i < values.length; i++) {
			String waarde = values[i];
			if (waarde == null || waarde.equals("")) {
				throw new ValueCannotBeEmptyException("De waarde van "
						+ names[i] + " mag niet leeg zijn");
			}
			if (waarde.equals("NULL")) {
				if (columns.get(names[i]).getMandatory()) {
					throw new ValueCannotBeEmptyException(
							"Record value met naam " + names[i]
									+ " mag niet leeg zijn");
				}
			}
		}

		// Komen de namen overeen van uit de primary key
		PrimaryKey primaryKey = getPrimaryKey();
		int aantalColumns = primaryKey.getColumns().size();
		if (aantalColumns == 0) {
			throw new PrimaryKeyDoesntExist("Primary Key is niet aangemaakt");
		}
		int intNames = 0;
		for(int i =0; i<names.length;i++) {
			System.out.println(names[i]);
		}
		for (Column column : primaryKey.getColumns().values()) { 
			//for (int i = 0; i < names.length; i++) {
				 System.out.println("Size " + aantalColumns + " " + getTableName() + " " + column.getName() + " " + names[intNames] + intNames);
				if (column.getName().equals(names[intNames]) && intNames + 1 <= aantalColumns) { 
					//break;
				} else if (intNames + 1 <= aantalColumns) {
					throw new PrimaryKeyColumnDoesntExist(
							"De kolumn met de naam " + names[intNames]
									+ " bestaat niet in de primary key");
				}
			//}
			intNames++;
		}
		// Bestaat er al een record met dezelfde primairy sleutel waarde
		for (Record record : records) {
			for (Column column : primaryKey.getColumns().values()) {
				int index = -1;
				for (int i = 0; i < names.length; i++) {
					if (column.getName().equals(names[i])) {
						index = i;
					}
				}

				if (index == -1) {
					throw new PrimaryKeyColumnDoesntExist("Column naam "
							+ column.getName()
							+ " komt niet voor in primairesleutel");
				} else {
					if (record.getRecordValues()[index].equals(values[index])) {
						throw new PrimaryKeyValueAlreadyExists("Een primarykey waarde bestaat al");
					}
				}
			}

		}
		// Controleren op references
		if (!constraint.getReferences().isEmpty()) {
			// doe voor alle foreignkeys
//			Reference[] refs = (Reference[]) constraint.getReferences().values()
//					.toArray();
			for(Reference ref : constraint.getReferences().values()) {
			//for (int i = 0; i < refs.length; i++) {
				// Stap2: Zoek Records op die een overeenkomst hebben in het
				// eerste attribuut van de foreignkeygroep
				//System.out.println("addRecord Reference " + refs[i].getName());

				Table toTable = RDBMS.getInstance().getDatabases()
						.get(ref.gettoGroup().getDbName()).getTables()
						.get(ref.gettoGroup().getTableName());
				Table fromTable = RDBMS.getInstance().getDatabases()
						.get(ref.getfromGroup().getDbName()).getTables()
						.get(ref.getfromGroup().getTableName());
				Entry<String, Column> columnEntry = ref.gettoGroup()
						.getColumns().entrySet().iterator().next();

				String filterKey = columnEntry.getValue().getName();

				ArrayList<String> filterValue = new ArrayList<String>();
				for (int j = 0; j < names.length; j++) {
					if (names[j].equals(filterKey)) {
						filterValue.add(values[j]);
					}
				}

				String[] arrayValues = filterValue.toArray(new String[0]);

				Record filteredRecord = null;
				try {
					filteredRecord = fromTable.findRecord(arrayValues);
				} catch (ValueNotFoundException e) {
					// Wordt niet gevonden maar het moet een een reference to
					// group fout worden
					throw new ReferenceToGroupDoesntExist("In Tabel "
							+ fromTable.getTableName()
							+ " moet een Record zijn met attribuut "
							+ filterKey + " op waarde " + filterValue);
				}

				// //Deze check of een een try catch
				// if(filteredRecord == null ||
				// filteredRecord.getRecordValues().length == 0) {
				// //WRONG Dit mag dus niet! throw Exception dat foreignkey
				// verwijst naar niet bestaand record
				// throw new ReferenceToGroupDoesntExist("In Tabel " +
				// toTable.getTableName() +
				// " moet een Record zijn met attribuut " + filterKey +
				// " op waarde " + filterValue);
				// }
			}
		}

		records.add(new Record(names, values));
	}

	public void deleteRecords(String key, String value)
			throws ColumnDoesntExistsException, ReferenceRecordException {

		ArrayList<Record> recordsToBeRemoved = new ArrayList<Record>();
		if (key != null && !key.equals("")) {

			getColumn(key);

			for (Record r : records) {
				if (r.getRecordValues()[r.columnPosition(key)].equals(value)) {
					recordsToBeRemoved.add(r);
//					System.out.println("Delete records: " + r.getRecordColumnNames().toString()
//							+ r.getRecordValues().toString());
				}
			}
		} else {
			//System.out.println("Verwijder alles");
			recordsToBeRemoved = (ArrayList<Record>) records.clone();
		}

		// Heeft een andere table een foreignkey naar deze table
		// Controleren op references
		for (Table tbl : RDBMS.getInstance().getDatabases().get(this.dbName).getTables().values()) {
			//Geen lege referenties en niet hetzelfde als this.table
			if (!tbl.getConstraint().getReferences().isEmpty() && tbl.getTableName() != this.getTableName()) {
				for (Reference ref : tbl.getConstraint().getReferences()
						.values()) {
					// Stap2: Zoek Records op die een overeenkomst hebben in het
					// eerste attribuut van de foreignkeygroep
					Table fromTable = RDBMS.getInstance().getDatabases()
							.get(ref.getfromGroup().getDbName()).getTables()
							.get(ref.getfromGroup().getTableName());
					Entry<String, Column> columnEntry = ref.gettoGroup()
							.getColumns().entrySet().iterator().next();
//					System.out.println("toTable: " +fromTable.getTableName());
//					System.out.println("tbl: " + tbl.getTableName());
//					System.out.println(columnEntry.getValue().getName());
					String columnName = columnEntry.getValue().getName();
					//Als de primarykey/toTable verwijst naar this.table controle houden. 
					if (fromTable.getTableName() == this.getTableName()) {
						//Door de te verwijderen records lopen
						for (Record thisR : recordsToBeRemoved) { //Record die verwijderd moet worden
							int thisRPosition = thisR.columnPosition(columnName);
							String[] thisValues = thisR.getRecordValues();
//							System.out.println(Arrays.toString(thisValues));
							for (Record fromR : tbl.getRecords()) {
								int fromRPosition = fromR.columnPosition(columnName);
								String[] fromValues = fromR.getRecordValues();
//								System.out.println("Posities " +thisRPosition + " " +fromRPosition);
//								System.out.println("Values: " +fromValues[fromRPosition] + " " + thisValues[thisRPosition]);
//								System.out.println(Arrays.toString(fromValues));
								if (thisValues[thisRPosition].equals(fromValues[fromRPosition])) {
									throw new ReferenceRecordException(
											"Kan niet een record verwijderen met een referentie naar een andere table");
								}

							}
						}
						
					}
				}
			}

		}
		records.removeAll(recordsToBeRemoved);
	}
	
	public Column getColumn(String theColumn)
			throws ColumnDoesntExistsException {
		Column column = columns.get(theColumn);
		if (columns.get(theColumn) == null) {
			throw new ColumnDoesntExistsException(theColumn);
		}
		return column;
	}

	public ArrayList<Record> selectRecords(String key, String value)
			throws ColumnDoesntExistsException {

		ArrayList<Record> foundRecords = new ArrayList<Record>();
		if (key != null && !key.equals("")) {

			getColumn(key);

			for (Record r : records) {
				if (r.getRecordValues()[r.columnPosition(key)].equals(value)) {
					foundRecords.add(r);
				}
			}
		} else {
			foundRecords = (ArrayList<Record>) records.clone();
		}
		return foundRecords;

	}

	/**
	 * Prints the records
	 */
	public void showRecords(){
		String format = "";
		
		System.out.println("--------------------------------------------");
		System.out.println("Printing table: " + tableName);
		for(String columnName : records.get(0).getRecordColumnNames()){
			if(columnName != null)
				format +=  "%-20s ";
		}
		System.out.println("--------------------------------------------");
		System.out.println(String.format(format, records.get(0).getRecordColumnNames()));
		System.out.println("--------------------------------------------");
		
		for (Record record : records) {
			System.out.println(String.format(format, record.getRecordValues()));
		}
		
	}
	/**
	 * Zoek naar een record
	 * 
	 * @param values
	 * @return
	 * @throws ValueNotFoundException
	 */
	public Record findRecord(String[] values) throws ValueNotFoundException {
		int aantalValues = values.length;
		int aantalgevondenValues = 0;
		Record gevondenRecord = null;
		for (Record record : records) {
			// Loop door de record values heen
			for (int ri = 0; ri < record.getRecordValues().length; ri++) {
				// Vergelijk
				for (int vi = 0; vi < aantalValues; vi++) {
					if (record.getRecordValues()[ri] == values[vi]) {
						aantalgevondenValues++;
					}
				}
			}
			if (aantalValues == aantalgevondenValues) {
				gevondenRecord = record;
				break;
			}
		}

		if (gevondenRecord == null) {
			throw new ValueNotFoundException("Record is niet gevonden");
		}

		return gevondenRecord;
	}

	public ArrayList<Record> getRecords() {
		return records;
	}

	public PrimaryKey getPrimaryKey() throws PrimaryKeyDoesntExist,
			PrimaryKeyHasNoColumns, ConstraintIsNull {
		PrimaryKey pk = null;

		if (constraint.getName().equals("null")) {
			throw new ConstraintIsNull("Constraint is niet aangemaakt");
		}
		for (GroupKey k : constraint.getKeys().values()) {
			if (k instanceof PrimaryKey) {
				if (k.getName().equals("") || k.getName().equals(null)) {
					throw new PrimaryKeyDoesntExist("PrimaryKey bestaat niet");
				}
				if (k.getColumns().size() == 0) {
					throw new PrimaryKeyHasNoColumns(
							"PrimaryKey bevat geen columns");
				}
				pk = (PrimaryKey) k;

			}
		}

		return pk;
	}

	public void addGroupKey(GroupKey aKey) throws DbNameDoesntMatchException,
			TableDoesntMatchException, ConstraintIsNull {
		if (aKey.getDbName().equals(dbName)) {
			if (aKey.getTableName().equals(tableName)) {
				if (constraint.getName().equals("null")) {
					throw new ConstraintIsNull("Contraint is niet aangemaakt");
				}
				constraint.addSingleKeys(aKey);
			} else {
				throw new TableDoesntMatchException("Tabelnaam komt niet overeen");
			}
		} else {
			throw new DbNameDoesntMatchException();
		}

	}

}
